iT邦幫忙

2025 iThome 鐵人賽

DAY 22
1
Odoo

Odoo × 生成式 AI:從零到一的企業自動化實戰系列 第 22

Day 22:從會計到人資的智慧化升級:AI 在各模組的應用

  • 分享至 

  • xImage
  •  

你將學到

  • 了解 Odoo 中 AI 在會計、HR、人資與 CRM 模組的應用,使日常作業更自動化與智慧化。
  • 掌握票據自動擷取發票/收據資訊與銀行對帳、自動履歷解析與人才推薦,以及銷售預測與商機評分模型的工作流程與原理。

關鍵字

智能推薦、銷售預測、多模組資料流、自動對帳、履歷解析、商機評分


AI 為何讓 Odoo 更聰明?

在當今數位轉型的浪潮中,Odoo 作為企業資源規劃 (ERP) 系統,正積極引入 人工智慧 (AI) 來強化各模組的功能。

想像一下,繁瑣的手動作業——例如逐張輸入發票、篩選上百份履歷或憑直覺預測銷售——現在都能透過 AI 自動完成或提供決策建議。

今天將以輕鬆但深入技術的方式,探索 Odoo 在會計、人資與 CRM 三大模組中引入 AI 的應用場景與實作方式。


會計模組:AI 在自動票據辨識的應用

應用情境

想像公司的財務人員每天都要處理大量發票與收據:報銷單據、人事費用單、供應商發票等等,手動輸入這些票據資訊不僅耗時,還容易出錯。

如果我們能引入 AI,自動讀取票據,將大幅減少人工輸入與比對的負擔。例如,員工提交了一疊報銷收據影本,AI OCR 引擎可即時擷取每張收據上的金額、日期、商家等關鍵資訊,自動填入費用申請單中。

接著,當公司的銀行交易記錄上線後,AI 模型會將銀行對帳單的每筆款項和系統中的應收應付項自動比對,如同替財務人員找出「對帳拼圖」的配對,快速完成對帳並標記可能的差異。透過 AI 的協助,傳統上需要人工作業的會計流程變得更高效準確

運作流程

AI 在會計票據處理與對帳中的具體運作流程:

ai-accounting

在上述流程中,當發票影像上傳至 Odoo 時,系統會觸發OCR (Optical Character Recognition, 光學字元辨識) 引擎讀取影像中的文字。

讀取成功後,AI 模型將擷取到的發票號碼、日期、金額、供應商等資訊自動填入 Odoo 中對應的會計憑證欄位。如果有些欄位信心度不高或模糊不清,系統會標記提醒人工確認,確保資料正確無誤。接著,AI 還會根據歷史記錄學習每個供應商常用的費用科目或發票類型,主動建議適當的會計科目,把發票自動分類到正確的帳簿中。

在對帳階段,AI 模型會將處理過的銀行對帳單逐筆交易與系統內發票、收據進行比對:當找到金額、日期相符的一對交易時,自動將它們勾稽並標記為已核對;若有無法匹配的交易,則輸出例外清單供會計人員後續人工檢視處理。整個流程大大減少了人工輸入與核對的工作量,且AI會隨著處理更多資料逐步提升準確率。

程式碼實作:OCR 發票識別

技術設計與資料流:

  • Wizard 上傳介面:建立一個繼承自 TransientModel 的 Wizard,包含上傳發票影像的欄位。使用者透過此 Wizard 選擇影像並點擊「匯入發票」按鈕啟動流程。
  • OCR 辨識調用:Wizard 的後端方法會將上傳的影像資料傳送給外部 OCR 服務(例如自建的 FastAPI OCR API 服務)進行文字辨識。服務回傳發票的結構化資訊,例如供應商名稱、發票日期、各項目品名與金額等。
  • 建立發票記錄:根據辨識結果,後端方法使用 Odoo ORM API 建立一筆發票(account.move),自動填入供應商、日期及發票行等欄位資料。這裡遵循 Odoo ORM 慣例使用 env['account.move'].create() 新建記錄,同時將發票影像以附件形式關聯到該記錄以供日後查閱。
  • 結果呈現:發票建立後,Wizard 方法返回一個動作 (act_window) 開啟新建立的發票表單,方便使用者檢視與確認。

發票 OCR 匯入 Wizard 範例

from odoo import models, fields, api
from odoo.exceptions import UserError
import base64, requests

class AccountInvoiceOCRWizard(models.TransientModel):
    _name = 'account.invoice.ocr.wizard'
    _description = 'Invoice OCR Import Wizard'

    invoice_image = fields.Binary(string="Invoice Image", attachment=True, required=True)
    filename = fields.Char(string="Filename")

    def action_import_invoice(self):
        # 確認已上傳發票圖片
        if not self.invoice_image:
            raise UserError("請先上傳發票影像檔。")
        # 呼叫外部 OCR API 辨識發票內容
        try:
            files = {'file': (self.filename or 'invoice.jpg', base64.b64decode(self.invoice_image))}
            response = requests.post('http://my-ocr-service/parse_invoice', files=files)
            if response.status_code != 200:
                raise Exception(f"OCR service returned status {response.status_code}")
            data = response.json()
        except Exception as e:
            raise UserError(f"OCR 辨識失敗: {e}")
        # 根據辨識結果組裝發票資料
        partner_name = data.get('vendor_name')
        # 嘗試匹配或新建供應商
        partner = self.env['res.partner'].search([('name', 'ilike', partner_name)], limit=1)
        if not partner and partner_name:
            partner = self.env['res.partner'].create({'name': partner_name, 'supplier_rank': 1})
        move_vals = {
            'move_type': 'in_invoice',  # 供應商帳單類型的發票
            'partner_id': partner.id if partner else None,
            'invoice_date': data.get('invoice_date'),
            'invoice_line_ids': []
        }
        # 處理發票明細行
        for line in data.get('lines', []):
            desc = line.get('description', '')
            qty = line.get('quantity', 1)
            price = line.get('price', 0.0)
            # 嘗試依名稱找到產品,不存在則使用描述建立臨時產品
            product = self.env['product.product'].search([('name', 'ilike', desc)], limit=1)
            if not product:
                product = self.env['product.product'].create({'name': desc, 'list_price': price})
            move_vals['invoice_line_ids'].append((0, 0, {
                'product_id': product.id,
                'quantity': qty,
                'price_unit': price,
                'name': desc or "OCR Item"
            }))
        # 建立發票 (account.move) 紀錄
        move = self.env['account.move'].create(move_vals)
        # 將原始影像附件到發票
        if move and self.filename:
            self.env['ir.attachment'].create({
                'name': self.filename,
                'res_model': 'account.move',
                'res_id': move.id,
                'type': 'binary',
                'datas': self.invoice_image,
            })
        # 返回動作以開啟新發票表單,讓使用者確認
        return {
            'name': "Invoice",
            'type': 'ir.actions.act_window',
            'res_model': 'account.move',
            'view_mode': 'form',
            'res_id': move.id,
            'target': 'current',
        }

上述程式碼中,我們建立了一個 account.invoice.ocr.wizard 繼承 TransientModel 的模型,用於上傳發票影像並執行 OCR。

action_import_invoice 方法先將 Binary 欄位中的影像轉換後,透過 requests.post 呼叫外部 OCR API,取得回傳的 JSON 資料。接著按照辨識的欄位填入 move_vals 字典:包含發票類型 (in_invoice 代表供應商發票)、供應商 (partner_id),發票日期、以及發票行項目等資訊。我們透過 Odoo ORM 在 account.move 模型中建立新紀錄並填入欄位。

為了遵循原生慣例,發票行的建立使用 one2many 欄位的 tuple 格式 (0, 0, values) 來新增多筆 invoice_line_ids。最後我們將原始發票影像存為附件關聯到新發票,並返回一個動作字典以開啟該發票的表單視圖。

Wizard 介面定義與動作 XML 範例

<odoo>
    <!-- 發票 OCR 匯入 Wizard 的表單視圖 -->
    <record id="view_invoice_ocr_wizard_form" model="ir.ui.view">
        <field name="name">account.invoice.ocr.wizard.form</field>
        <field name="model">account.invoice.ocr.wizard</field>
        <field name="arch" type="xml">
            <form string="Import Invoice via OCR">
                <group>
                    <field name="invoice_image" filename="filename"
                           widget="image" class="oe_enterprise_hide" />
                    <field name="filename" invisible="1"/>
                </group>
                <footer>
                    <button name="action_import_invoice" type="object" string="匯入發票" class="btn-primary"/>
                    <button string="取消" class="btn-secondary" special="cancel"/>
                </footer>
            </form>
        </field>
    </record>

    <!-- 操作動作:開啟 Wizard -->
    <record id="action_invoice_ocr_wizard" model="ir.actions.act_window">
        <field name="name">發票影像匯入</field>
        <field name="res_model">account.invoice.ocr.wizard</field>
        <field name="view_mode">form</field>
        <field name="target">new</field>
    </record>

    <!-- 導覽功能表:將 Wizard 動作放入會計模組選單 -->
    <menuitem id="menu_action_invoice_ocr" name="發票影像匯入" parent="account.menu_finance" action="action_invoice_ocr_wizard" sequence="90"/>
</odoo>

以上 XML 定義了一個 Wizard 表單視圖,包含影像上傳欄位及「匯入發票」按鈕。ir.actions.act_window 設定使我們可以從選單或其他地方開啟這個 Wizard。最後透過 <menuitem> 將此動作掛載到會計模組的選單中 (在此以財務總管 account.menu_finance 父選單下新增一項)。當使用者點選「發票影像匯入」時,會彈出 Wizard 表單,讓使用者上傳發票掃描檔並執行 OCR 導入。

這個擴充遵循 Odoo 的標準開發模式:使用 TransientModel 實現 Wizard 流程,不修改核心模型結構。同時,我們將外部服務解耦出Odoo,由 FastAPI 提供 OCR 功能,透過 HTTP API 交換資料,保持系統模組化。

辨識完成後自動建立的發票記錄,讓會計人員只需簡單校對即可過帳,大幅減少人工輸入時間與錯誤風險**。由於OCR 辨識可能有誤差**,我們保留讓使用者在發票表單中編輯修正的彈性,並將原始影像作為附件保存以供日後稽核。

另外整合時要注意資料安全與隱私:發票與帳務資料較敏感,如果使用外部雲端 OCR 服務,要確保符合公司政策(可對影像做脫敏處理或簽訂保密協議)。

由於會計資料牽涉合規,我們在導入 AI 時,流程上通常仍保留人工覆核環節,以確保關鍵財務數字無誤。


人資模組:智能履歷審查與訓練推薦的應用

應用情境

現代的人力資源 (HR) 部門經常面臨兩大挑戰:招聘培訓。在招聘方面,一個熱門職缺往往會湧入上百份履歷,人工逐一篩選不僅耗費人力,也可能因主觀疲勞而疏漏人才。

而在員工發展方面,HR 需要為在職員工規劃培訓課程,以縮小技能差距,但找出每位員工適合的進修方向並非易事。

讓我們想像這樣的情境:HR 招募團隊每天開啟滿滿的收件匣,裡面是求職者們寄來的 PDF/Word 履歷;同時培訓專員要盤點公司未來的技能需求,安排現有人才進修。如果引入 AI,這些任務將大為簡化——智能履歷審查工具會自動讀取每份履歷,提取關鍵資訊如學歷、技能、工作經驗,甚至根據職缺要求進行候選人評分;而針對在職員工,AI 可以分析其績效和職涯目標,提供個人化訓練課程推薦,打造量身定制的成長計劃。

具體而言,當人資招募收到大量應徵履歷時,AI 會像一位資深獵才顧問,快速掃描每份 CV 的內容——擷取出求職者的學歷背景、技能清單與過往經驗年數,與該職位需求進行匹配,產出一個適配度分數或排名。HR 人員據此可以優先聯繫高分候選人,加速招募流程。同時,對公司現有員工,AI 模型會比對他們的現有技能與公司未來發展所需技能,找出技能缺口,並從內部/外部的課程資料庫中推薦合適的培訓課程

運作流程

下面以流程圖呈現履歷解析員工訓練推薦兩部分的 AI 流程:

ai-hr-1

ai-hr-2

招聘流程中,當新的電子履歷上傳或郵件收到時,AI 會首先對文件進行解析。如果履歷是 PDF 或圖片掃描件,會先經過文字識別 ;接著模型讀取履歷文字,提取出結構化資訊。

例如從段落中抓取學歷(如「碩士,資訊管理」)、技能(如「Python編程、專案管理」)、工作年資關鍵成果等欄位。若遇到無法解析的特殊格式,系統會標示需要人工補充。當候選人的關鍵資料都結構化後,AI 會將其與職缺需求進行比對:比如該職位要求「5年以上經驗、熟悉數據分析」,則模型會根據履歷中的經驗年數和技能關鍵字計算匹配度分數。最終,AI 給出每位候選人一個分數或評級,HR 系統將候選人列表依此排序,方便招募人員優先篩選。得分特別高的,可能自動標記為「強烈建議面試」;反之,低分的則列為備選或淘汰。這樣一來,人資可以將精力集中在最有潛力的幾位候選人上,大幅提高招聘效率。

接下來看在職培訓部分。這裡 AI 扮演著員工的「訓練顧問」。它首先彙整每名員工的技能檔案績效評估資料。例如,員工張小明的檔案顯示他精通 Java 和 SQL,近期績效評估中提到希望他加強雲端部署技能。AI 模型會將公司的未來戰略需求(例如更多雲端專才)和員工現有技能做比對,得出每個員工的技能差距報告

接著,AI 連結到企業內建或外部的課程資料庫(如線上課程列表、研討會資訊),利用演算法尋找能彌補該員工技能差距的課程。例如辨識出張小明缺少 AWS 雲端經驗,就從資料庫中找出「AWS雲服務入門」課程推薦給他。AI 可以根據員工職位、學習偏好甚至同事的培訓成功案例,給出一份個人化的課程清單

之後,系統可能將推薦結果發送給員工本人或其直屬經理以供審核,經討論確認後,HR 將選定的課程納入該員工的培訓計畫並安排後續的報名和跟進。如此,每位員工都獲得專屬的學習發展路線,有助於提升整體技能水準留住人才,因為員工也會感受到公司在積極投資他的成長。

程式碼實作:履歷分析與評分

技術設計與資料流:

  • 欄位擴充:透過 _inherit = 'hr.applicant' 在應徵者模型中新增兩個欄位:resume_data (Text) 用於儲存解析後的履歷重點或結構化資料摘要,ai_score (Float) 用於儲存 AI 給予候選人的評分。此外,我們也新增一個 resume_file 二進位欄位讓使用者上傳履歷 PDF 檔(或從附件取得),以及對應的檔名欄位。
  • 解析操作按鈕:在 hr.applicant 表單上加入「解析履歷」按鈕,呼叫後端方法 action_parse_resume。該方法會將履歷 PDF 傳送給外部 FastAPI 服務的 API(例如 POST /parse_resume),由 AI 模型提取出求職者的關鍵資訊(如姓名、經驗、技能)並進行評分,回傳 JSON 結構資料。
  • 資料寫入與呈現:後端接收到解析結果後,將結構化資訊轉為 JSON 字串或文字摘要寫入 resume_data,將建議分數寫入 ai_score。這些欄位在表單中為唯讀顯示,供 HR 人員檢視。HR 可根據 AI 提供的摘要與分數快速瞭解該候選人的概況和推薦程度,輔助決策。

hr.applicant 模型擴充與履歷解析方法範例

from odoo import models, fields, api
from odoo.exceptions import UserError
import base64, json, requests

class Applicant(models.Model):
    _inherit = 'hr.applicant'

    resume_file = fields.Binary(string="Resume PDF")
    resume_filename = fields.Char(string="Resume Filename")
    resume_data = fields.Text(string="Parsed Resume Data", readonly=True)
    ai_score = fields.Float(string="AI 評分", readonly=True)

    def action_parse_resume(self):
        # 確認已上傳履歷檔案
        if not self.resume_file:
            raise UserError("請先上傳候選人的履歷檔案。")
        # 呼叫外部 FastAPI 履歷解析服務
        try:
            file_content = base64.b64decode(self.resume_file)
            files = {'file': (self.resume_filename or 'resume.pdf', file_content, 'application/pdf')}
            response = requests.post('http://my-ai-service/parse_resume', files=files)
            if response.status_code != 200:
                raise Exception(f"Service returned status {response.status_code}")
            result = response.json()
        except Exception as e:
            raise UserError(f"履歷解析失敗: {e}")
        # 取得解析結果中的結構資料與分數
        structured_data = result.get('structured_data')
        score = result.get('score')
        # 將結構化資料轉存為 JSON 字串或摘要文字,存入 resume_data 欄位
        if structured_data:
            self.resume_data = json.dumps(structured_data, ensure_ascii=False, indent=2)
        elif result.get('summary'):
            # 若有提供摘要欄位,則存入摘要
            self.resume_data = result['summary']
        else:
            self.resume_data = "(No data parsed)"
        # 存入 AI 評分
        self.ai_score = score if score is not None else 0.0
        return True

上述程式碼透過 _inherit 擴充現有的 hr.applicant 模型,在 ORM 層級新增了我們需要的欄位和方法。action_parse_resume 方法的邏輯如下:

  1. 資料準備:將二進位的履歷檔 (resume_file) 解碼成原始 PDF 檔案內容,並使用 Python requests 套件發出 HTTP 請求到 FastAPI 服務。這裡假設 FastAPI 提供的服務接受一個名為 "file" 的檔案上傳欄位,URL 例如 http://my-ai-service/parse_resume
  2. 錯誤處理:若 HTTP 回應非 200,或請求過程中發生例外,我們捕捉錯誤並以 UserError 呈現,通知使用者解析失敗。
  3. 結果處理:將 API 回傳的 JSON 結果拆解出所需欄位。例如我們預期它可能包含 structured_data(例如字典含姓名、電話、技能等欄位)以及整體評分 score。我們將結構化資料轉為易讀的 JSON 字串儲存到 resume_data(方便 HR 在前端直接查看重點資訊),將分數存入 ai_score。欄位定義中已標記 readonly=True,確保這些 AI 產出值不被手動修改。
  • 設計考量:之所以將解析資料存成文字欄位而非直接填入其他欄位,是為了簡化範例並提供彈性:HR 能直觀查看 AI 提取的履歷重點摘要,若有需要仍可手動編輯或將資訊轉錄到其他自訂欄位中。實務上,亦可將 structured_data 中的關鍵欄位(如技能、年資)對應到 Odoo 模型的相應欄位做自動填寫,但這需要確保資料欄位匹配且格式正確,此處不贅述。

應徵者表單視圖擴充 XML 範例

<odoo>
    <!-- 繼承 Odoo Recruitment 模組的 Applicant 表單視圖來添加新欄位和按鈕 -->
    <record id="view_applicant_form_inherit_ai" model="ir.ui.view">
        <field name="name">hr.applicant.form.inherit.ai</field>
        <field name="model">hr.applicant</field>
        <field name="inherit_id" ref="hr_recruitment.view_applicant_form"/>
        <field name="arch" type="xml">
            <!-- 在應徵者表單頁面添加一個分組,放置履歷上傳欄位、解析按鈕、以及結果欄位 -->
            <group string="履歷解析">
                <field name="resume_file" filename="resume_filename" widget="binary"/>
                <button name="action_parse_resume" type="object" string="解析履歷" class="btn-primary"/>
            </group>
            <group string="AI 解析結果">
                <field name="ai_score" />
                <field name="resume_data" widget="text" colspan="2" />
            </group>
        </field>
    </record>
</odoo>

上述 XML 使用視圖繼承 (inherit_id) 的方式,將我們的新欄位與按鈕插入到現有的應徵者表單畫面上。在第一個 <group> 中,我們放置了履歷檔案上傳欄位及「解析履歷」按鈕;在第二個 <group> 中放置AI 評分解析後資料欄位,方便 HR 查看結果。widget="text" 使得 resume_data 以純文字區塊呈現,可顯示JSON或段落文字格式的內容。

當 HR 人員打開某個應徵者記錄並上傳候選人的履歷 PDF 後,只需點擊「解析履歷」按鈕,系統即會將檔案傳至 AI 服務並迅速得到分析結果。

幾秒內,表單上的「AI 評分」會顯示如 85/100 這樣的分數,而「解析結果」欄位則呈現關鍵資訊摘要,例如:「經歷:5年軟體開發經驗;技能:Python、Docker、管理經驗...」。

藉由這種自動解析與評分,HR 可以快速篩選出高潛力的候選人而將精力投入到深入面試中。值得注意的是,AI 分析只是輔助,HR 仍應最終判斷候選人適配度;我們設計上保留了人工覆核的空間(例如可以對解析出的內容再做調整)。

整體流程遵循 Odoo ORM 方式擴充模型與視圖,不影響既有招聘模組的資料表結構與功能,符合模組化原則。

💡 Gary’s Pro Tip|避免招聘偏見
在讓 AI 幫助履歷篩選時,要小心模型可能學到了數據中不良的偏見。例如過去公司偏好特定學校畢業的人才,導致模型自動降低非該校申請者分數。為避免這種情況,建議在設計候選人評分標準時,避開種族、性別、年齡等敏感資訊,主要關注技能和經驗相關欄位。此外,可定期審查AI的推薦結果,如發現不合理的排除/選擇,需調整演算法或加入人工複核機制。


CRM 模組:AI 在預測銷售與商機評分的應用

應用情境

進入 CRM (Customer Relationship Management) 領域,我們關注的是銷售預測商機評分 (Lead Scoring)

銷售團隊常常要回答兩個問題:「下季度業績會如何?」以及「目前這些潛在客戶中,哪些最有可能成交?」。傳統上,銷售預測可能依賴經理人在 Excel 中根據往年數據做粗略估計,而商機評分則靠業務員的直覺經驗去判斷誰是「大條魚」。

AI 的加入,讓這一切有了科學的依據:利用歷史銷售資料訓練的預測模型,可以對未來幾個月的營收做出相對精確的預測;而基於客戶和交易數據的機器學習模型則能為每筆銷售線索打分,指出哪些 leads 最值得優先跟進。

運作流程

以下流程圖將展示銷售預測與商機評分這兩項 AI 功能在 CRM 模組中的運作:

ai-crm-1

ai-crm-2

銷售預測部分,首先需要有大量歷史銷售資料作為基礎,包含每期(每月、每季)的銷售額、訂單數量,以及可能的影響因素(產品類別、季節性、促銷活動等)。

將這些數據送入預測模型訓練階段,可以使用各種時間序列模型或機器學習算法,例如 ARIMA、Prophet、XGBoost 等。模型訓練完成後,就得到了一個能夠輸入時間序列特徵並預測未來銷售額的 AI 模型。

接著,我們將當前的銷售管線數據(例如本季度已簽單金額、正在談判的商機金額等)輸入模型,讓它預測下一季度的銷售額或訂單數。模型的預測結果會匯總成報告,可能以圖表形式顯示未來幾個月的銷售曲線、相對的信心區間等等。

有了這份報告,管理層就能以更科學的依據來制定營業目標、調整庫存和資源配置,避免純粹憑經驗拍腦袋。同時,若預測顯示某月可能業績下滑,團隊可以提前啟動行銷活動或促銷方案作為應對。

商機評分部分,AI 模型做的是分類/排序的工作。每當新的潛在客戶 (Lead/Opportunity) 進入系統,模型就像一個「顧問」,幫你快速評估它的價值。首先,系統會對商機資料進行特徵提取:例如抽取出「來源=官網註冊」、「產業=製造業」、「公司規模=50人」、「瀏覽官網頁面數=5次」、「與銷售互動=2封Email」等等。接著,這些特徵被餵給預先訓練好的機器學習模型

該模型可能是一個分類模型(預測該lead最終成交(Won)或流失(Lost)的概率)。模型基於過去成千上萬條類似客戶的結果來預測這一條的結局機率。例如它算出「成交概率 85%」,我們可以將此轉換為一個分數(比如五星中的四星半,或百分比形式顯示)。AI 甚至還可以自動豐富這些潛在客戶資料,例如根據Email網域自動填充公司的行業、規模等資訊,讓評分更準確。

最終,高分的商機會被優先指派給資深業務跟進或立刻聯絡,而低分的則可能交由自動化行銷流程去培養(比如定期發送電子報維繫)。如此一來,銷售團隊能將時間用在刀刃上,提高整體轉換率和營收。

程式碼實作:銷售預測與商機評分

技術設計與流程:

  • 欄位擴充:使用 _inherit = 'crm.lead' 為商機模型新增 ai_win_probability (Float) 欄位,用於記錄預測的成交率百分比,以及 ai_priority_label (Selection) 欄位,表示 AI 建議的優先級(例如 Low/Medium/High 三檔分類)。
  • AI 預測按鈕:在商機表單中加入「AI 預測」按鈕,綁定方法 action_predict_lead。當使用者點擊時,後端會收集該 crm.lead 的相關資訊(例如名稱、預計收入、產業、備註描述等),以 JSON 格式傳送給 FastAPI 的 GPT 模型端點 (假設為 POST /score_lead)。
  • GPT 分析回傳:FastAPI 背後可能連接 OpenAI GPT-5 或其他模型,依據商機資料給出建議的成交機率及優先級分類,返回 JSON 內容,例如 {"win_probability": 0.75, "priority": "high"}
  • 寫入與提示:Odoo 後端取得結果後,將 win_probability 轉換成百分比填入欄位,priority 字串對應到定義的 Selection 選項填入 ai_priority_label。資料儲存後,使用者界面即刻顯示 AI 評估的數值。開發時也可加入通知 (notification) 來提示評估完成與結果概要。

crm.lead 模型擴充與AI評估方法範例

from odoo import models, fields, api
from odoo.exceptions import UserError
import requests

class Lead(models.Model):
    _inherit = 'crm.lead'

    ai_win_probability = fields.Float(string="AI 成交率 (%)", readonly=True)
    ai_priority_label = fields.Selection([
        ('low', 'Low'), ('medium', 'Medium'), ('high', 'High')
    ], string="AI 建議優先級", readonly=True)

    def action_predict_lead(self):
        # 準備商機資料發送給 AI 服務
        payload = {
            'lead_name': self.name,
            'lead_value': self.planned_revenue,
            'customer_industry': self.partner_id.industry_id.name if self.partner_id.industry_id else '',
            'description': self.description or ''
        }
        try:
            response = requests.post("http://my-ai-service/score_lead", json=payload)
            if response.status_code != 200:
                raise Exception(f"Bad response: {response.status_code}")
            result = response.json()
        except Exception as e:
            raise UserError(f"AI 預測服務調用失敗: {e}")
        # 解析回傳結果並寫入欄位
        win_prob = result.get('win_probability')  # 預測成交率 (0~1 之間的小數)
        priority = result.get('priority')         # 預測優先級 (low/medium/high)
        if win_prob is not None:
            # 轉換成百分比存入,例如0.75變75%
            self.ai_win_probability = float(win_prob) * 100.0
        if priority:
            # 如結果在預定義選項內則寫入,否則預設為 medium
            self.ai_priority_label = priority if priority in dict(self._fields['ai_priority_label'].selection) else 'medium'
        return True

在上述程式中,我們使用 _inherit 為商機模型添加了新的 AI 欄位以及操作方法。action_predict_lead 所做的事情包含:

  1. 資料打包:將商機的主要資訊組成 payload 字典。其中我們選取了可能影響成交率的欄位:商機名稱、預計營收 (planned_revenue)、客戶的產業、商機描述等。這些欄位只是示例,實際應用中可根據模型需要傳遞更多訊息(如商機階段、客戶規模等)。
  2. 呼叫 AI 接口:透過 requests.post 發送 JSON 至 FastAPI 的預測接口。如果 API 回應非成功,我們拋出異常提示。這裡的設計是同步調用,即按鈕按下會等待 AI 結果返回,在一定時間內(幾秒內)完成評估。
  3. 結果處理:解析 JSON 結果,將成交率 (win_probability) 乘以100轉換為百分比數字寫入 ai_win_probability 欄位;將優先級 (priority) 文字寫入 ai_priority_label。我們用了一個保險處理:確認回傳的 priority 值在我們定義的 low/medium/high 範圍內,否則給個中性值,以避免無效值存入。欄位更新後,Odoo 會自動在畫面上反映最新值。

值得一提的是,上述方法返回 True,Odoo 在處理按鈕類型為 type="object" 的點擊時,不要求特定回傳值;如需在操作完成後給使用者彈出訊息,可使用 self.env.user.notify_info(...) 類的方法,這裡留待需要時實現。

商機表單視圖與按鈕擴充 XML 範例

<odoo>
    <record id="view_crm_lead_form_inherit_ai" model="ir.ui.view">
        <field name="name">crm.lead.form.inherit.ai</field>
        <field name="model">crm.lead</field>
        <field name="inherit_id" ref="crm.crm_lead_view_form"/>
        <field name="arch" type="xml">
            <!-- 在商機表單的「內部備註」頁籤後新增一個頁籤來顯示 AI 評估結果 -->
            <xpath expr="//page[@name='internal_notes']" position="after">
                <page string="AI 評估" name="ai_prediction">
                    <group>
                        <button name="action_predict_lead" type="object" string="AI 預測評分" class="btn-primary"/>
                        <field name="ai_win_probability" />
                        <field name="ai_priority_label" />
                    </group>
                </page>
            </xpath>
        </field>
    </record>
</odoo>

以上 XML 繼承了 CRM 商機的主要表單視圖 (crm.crm_lead_view_form),透過 XPath 在「內部備註」頁籤後新增了一個名為 "AI 評估" 的頁籤。其中包含一個按鈕和兩個唯讀欄位:按鈕點擊後將調用先前定義的 action_predict_lead 方法,而 ai_win_probabilityai_priority_label 欄位用於顯示預測的成交率百分比和優先級等第。這種分頁設計可以避免擠占原有畫面空間,使用者可按需點擊查看 AI 分析結果。

業務人員在使用本功能時,只需打開商機記錄點擊「AI 預測評分」,幾秒內便能看到例如「AI 成交率:82.5%,建議優先級:High」的結果出現在「AI 評估」頁籤下。

💡 Gary’s Pro Tip|預測結果可解釋性
AI 給出的預測如果無法解釋理由,業務團隊有時會存疑。例如模型突然判定某老客戶的單子不可能成交,而業務員可能認為勝券在握,這時就需要解釋模型判斷的依據(可能因為該客戶過去3次相似需求最後都沒購買)。一種做法是提供解釋性提示,例如附帶一句「因客戶過去一年無追加訂單且競品報價存在,建議降低預期」。這可透過 SHAP 等模型解釋工具取得,但在 Odoo 中實現需要工程投入。權衡下,可以先透過訓練簡單可解釋的模型(如決策樹,以規則呈現)與團隊分享,培養大家對AI決策邏輯的理解,再逐步換用複雜模型並提供解釋摘要。


今日結語

透過以上三大模組的探索,我們看到 AI 結合 Odoo 的會計、人資、CRM 模組,為管理帶來前所未有的效率提升智慧洞察

從繁瑣的票據處理到海量的履歷篩選,從未知的未來業績到茫茫的潛在客戶,AI 都能成為 Odoo ERP 中可靠的助手。

當然,在技術實現的背後,我們也需要關注數據品質、模型偏差和用戶採用度等因素,才能讓AI真正落地生根。


上一篇
Day 21:法律 AI 導入的挑戰與對策
下一篇
Day 23:企業級安全堡壘:資料保護與合規性指南
系列文
Odoo × 生成式 AI:從零到一的企業自動化實戰24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言